package com.yonghui.summer.code.dao;

import com.squareup.javapoet.*;
import com.yonghui.summer.annotation.Dao;
import com.yonghui.summer.annotation.DaoComponent;
import com.yonghui.summer.annotation.DaoSkip;
import com.yonghui.summer.code.dao.handler.MybatisParamsHandler;
import com.yonghui.summer.code.dao.utils.ProcessorHelper;
import com.yonghui.summer.utils.SqlHelper;
import com.yonghui.summer.dao.AndroidDao;
import com.yonghui.summer.po.MybatisParam;
import com.yonghui.summer.po.MybatisSql;
import com.yonghui.summer.po.ParamValue;
import com.yonghui.summer.po.SqlParams;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;

import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.*;
import javax.lang.model.util.Elements;
import javax.tools.Diagnostic;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.*;

import static javax.lang.model.SourceVersion.latestSupported;


/**
 * Created by yangchuan on 16/12/8.
 */
//@AutoService(Processor.class)
@SupportedAnnotationTypes({"com.yonghui.summer.annotation.Dao"})
public class DaoProcessor extends AbstractProcessor {

    private static final String SUFFIX_IMPL = "Impl";

    private static final String SUFFIX_ANDROID = "Android";

    private static final Type arraysType = (new Object[][]{}).getClass().getComponentType();

    private static final String LINE_END = ";\n";

    private static final String ANDROID_DAO = "androidDao";

    private Elements elementUtils;
    private Filer filer;
    private Messager messager;


    private MybatisParamsHandler mybatisParamsHandler;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        elementUtils = processingEnv.getElementUtils();
        filer = processingEnv.getFiler();
        messager = processingEnv.getMessager();
        mybatisParamsHandler = new MybatisParamsHandler(messager, elementUtils);
    }

    /**
     * {@inheritDoc}
     *
     * @param annotations
     * @param roundEnv
     */
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        return /*generateAndroidDao(annotations, roundEnv) &&*/ generateDaoImpl(annotations, roundEnv);
    }

    private boolean generateDaoImpl(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        messager = processingEnv.getMessager();
        filer = processingEnv.getFiler();

        String className = null;
        MethodSpec.Builder method = null;

        for (Element e : roundEnv.getElementsAnnotatedWith(Dao.class)) {
            if (e.getKind() != ElementKind.INTERFACE) {
                messager.printMessage(Diagnostic.Kind.ERROR, String.format("Only interface can be annotated with @%s", Dao.class.getSimpleName()));
                return true;
            }
            className = e.toString();
            messager.printMessage(Diagnostic.Kind.NOTE, "class.name----:" + className);
            TypeElement superClassName = elementUtils.getTypeElement(className);
            AnnotationSpec.Builder daoCompmentBuilder = AnnotationSpec.builder(DaoComponent.class);
            Dao dao = e.getAnnotation(Dao.class);
            if (dao.value() != null && dao.value().length() > 0) {
                daoCompmentBuilder.addMember("value", "$S", dao.value());
            }

            String name = e.getSimpleName().toString();
            String daoInstanceName = name.substring(0, 1).toLowerCase() + name.substring(1);

            TypeSpec.Builder classBuilder = TypeSpec.classBuilder(name + SUFFIX_IMPL)
                    .addModifiers(Modifier.PUBLIC)
                    .addAnnotation(Slf4j.class)
                    .addAnnotation(daoCompmentBuilder.build())
                    .addSuperinterface(TypeName.get(superClassName.asType()));

            String androidDaoClass = className + SUFFIX_ANDROID;
            messager.printMessage(Diagnostic.Kind.NOTE, "generated android dao:" + androidDaoClass);

            FieldSpec.Builder fsb = FieldSpec.builder(TypeName.get(AndroidDao.class), ANDROID_DAO)
                    .addAnnotation(Autowired.class)
                    .addModifiers(Modifier.PRIVATE);
            classBuilder.addField(fsb.build());

            /*String instanceName = "androidContext";
            FieldSpec.Builder fsb = FieldSpec.builder(TypeName.get(AndroidContext.class), instanceName)
                    .addAnnotation(Autowired.class)
                    .addModifiers(Modifier.PRIVATE);
            classBuilder.addField(fsb.build());*/


            for (Element enclosedElement : e.getEnclosedElements()) {
                if (enclosedElement.getKind() == ElementKind.METHOD) {

                    ExecutableElement amethod = (ExecutableElement) enclosedElement;
                    if (!amethod.getModifiers().contains(Modifier.PUBLIC)) {
                        continue;
                    }
                    String methodName = enclosedElement.getSimpleName().toString();
                    messager.printMessage(Diagnostic.Kind.NOTE, "method:" + methodName);
                    method = MethodSpec.methodBuilder(methodName)
                            .addAnnotation(Override.class)
                            .addModifiers(Modifier.PUBLIC).returns(TypeName.get(amethod.getReturnType()));

                    if (enclosedElement.getAnnotation(DaoSkip.class) != null) {
                        for (VariableElement ve : amethod.getParameters()) {
                            ParameterSpec.Builder psb = ParameterSpec.builder(TypeName.get(ve.asType()),  ve.getSimpleName().toString());
                            method.addParameter(psb.build());
                        }
                        method.addStatement("log.error(\"调用了"+name+"未开放的"+methodName+"方法\"); \n" +
                                "return null");
                        classBuilder = classBuilder.addMethod(method.build());
                        continue;
                    }

                    MybatisSql mybatisSql = SqlHelper.getMybatisSql(enclosedElement, messager);
                    MybatisParam mybatisParam = null;
                    String arg = null;
                    StringBuilder paramValueBuilder = new StringBuilder();
                    StringBuilder mapBuilder = new StringBuilder();
                    for (VariableElement ve : amethod.getParameters()) {
                        arg = ve.getSimpleName().toString();
                        mybatisParam = ProcessorHelper.getParam(ve, arg);
                        mapBuilder.append("map.put(\"").append(mybatisParam.getName()).append("\",").append(mybatisParam.getArgs()).append(")").append(LINE_END);
                        paramValueBuilder.append(mybatisParamsHandler.generateParamValue(ve));
                        ParameterSpec.Builder psb = ParameterSpec.builder(TypeName.get(ve.asType()), mybatisParam.getArgs());
                        method.addParameter(psb.build());
                    }

                    StringBuilder statementBuilder = new StringBuilder();
                    statementBuilder.append(Map.class.getName()).append(" map = new ").append(HashMap.class.getName()).append("<>()").append(LINE_END)
                            .append(mapBuilder.toString());
                    if (mybatisSql.getFromType() == MybatisSql.FROM_TYPE_STATIC) {
                        statementBuilder.append("String sql = \"").append(mybatisSql.getSql()).append("\"").append(LINE_END);
                    } else {
                        statementBuilder
                                .append("String sql=").append(mybatisSql.getSql()).append("(map)").append(LINE_END);
                    }

                   // statementBuilder.append(List.class.getName()).append("<").append(ParamValue.class.getName()).append("> pvs = new ").append(ArrayList.class.getName()).append("<>()").append(LINE_END)
                     //       .append(paramValueBuilder.toString());
                    //      .append(SqlParams.class.getName())
                     //     .append(" sps = ").append(SqlHelper.class.getName()).append(".analyseSql(sql,pvs)").append(LINE_END);
                    if(!mybatisSql.getAndroidMethod().equals("execSQL")) {// map 替换 sps.getSqlParams()， sql 替换 sps.getSql()
                        statementBuilder.append("return ("+amethod.getReturnType().toString()+")" + ANDROID_DAO + "." + mybatisSql.getAndroidMethod() + "(\"" + dao.db().name() + "\",sql,map,\"" + amethod.getReturnType().toString() + "\")");
                    }else{
                        statementBuilder.append("return ("+amethod.getReturnType().toString()+")" + ANDROID_DAO + "." + mybatisSql.getAndroidMethod() + "(\"" + dao.db().name() + "\",sql,map)");

                    }
                    method.addStatement(statementBuilder.toString());

                    classBuilder = classBuilder.addMethod(method.build());
                }
            }


            PackageElement pkg = elementUtils.getPackageOf(superClassName);
            String packageName = pkg.isUnnamed() ? null : pkg.getQualifiedName().toString();
            if (packageName == null) {
                messager.printMessage(Diagnostic.Kind.ERROR, "@Dao find package name is null.");
                return true;
            }

            TypeSpec typeSpec = classBuilder.build();
            // Write file
            try {
                JavaFile.builder(packageName, typeSpec).build().writeTo(filer);
            } catch (IOException e1) {
                messager.printMessage(Diagnostic.Kind.ERROR, e1.getMessage());
                return true;
            }
        }

        return true;
    }

    private boolean generateAndroidDao(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        messager = processingEnv.getMessager();
        filer = processingEnv.getFiler();

        String className = null;
        MethodSpec.Builder method = null;

        String urls = null;
        for (Element e : roundEnv.getElementsAnnotatedWith(Dao.class)) {
            if (e.getKind() != ElementKind.INTERFACE) {
                messager.printMessage(Diagnostic.Kind.ERROR, String.format("Only interface can be annotated with @%s", Dao.class.getSimpleName()));
                return true;
            }
            className = e.toString();
            TypeElement superClassName = elementUtils.getTypeElement(className);
            TypeSpec.Builder classBuilder = TypeSpec.interfaceBuilder(e.getSimpleName() + SUFFIX_ANDROID)
                    .addModifiers(Modifier.PUBLIC);


            for (Element enclosedElement : e.getEnclosedElements()) {
                if (enclosedElement.getKind() == ElementKind.METHOD) {
                    if (enclosedElement.getAnnotation(DaoSkip.class) != null) {
                        continue;
                    }

                    ExecutableElement amethod = (ExecutableElement) enclosedElement;
                    if (!amethod.getModifiers().contains(Modifier.PUBLIC)) {
                        continue;
                    }
                    String methodName = enclosedElement.getSimpleName().toString();
                    messager.printMessage(Diagnostic.Kind.NOTE, "method.name:" + methodName);


                    List<? extends AnnotationMirror> ams = enclosedElement.getAnnotationMirrors();

                    method = MethodSpec.methodBuilder(methodName)
                            .addModifiers(Modifier.ABSTRACT)
                            // .addAnnotation(Override.class)
                            .addModifiers(Modifier.PUBLIC).returns(TypeName.get(amethod.getReturnType()));

                    List<MybatisParam> mybatisParams = new ArrayList<>();
                    MybatisParam mybatisParam = null;

                    for (VariableElement ve : amethod.getParameters()) {
                        String arg = ve.getSimpleName().toString();
                        mybatisParam = ProcessorHelper.getParam(ve, arg);
                        mybatisParams.add(mybatisParam);

                    }

                    ParameterSpec.Builder psb = ParameterSpec.builder(TypeName.get(String.class), "sql");
                    method.addParameter(psb.build());
                    psb = ParameterSpec.builder(TypeName.get(arraysType), "mybatisParams");

                    method.addParameter(psb.build());

                    classBuilder = classBuilder.addMethod(method.build());
                }
            }


            PackageElement pkg = elementUtils.getPackageOf(superClassName);
            String packageName = pkg.isUnnamed() ? null : pkg.getQualifiedName().toString();
            if (packageName == null) {
                messager.printMessage(Diagnostic.Kind.ERROR, "@Dao find package name is null.");
                return true;
            }

            TypeSpec typeSpec = classBuilder.build();
            // Write file
            try {
                JavaFile.builder(packageName, typeSpec).build().writeTo(filer);
            } catch (IOException e1) {
                messager.printMessage(Diagnostic.Kind.ERROR, e1.getMessage());
                return true;
            }
        }

        return true;
    }

    @Override
    public SourceVersion getSupportedSourceVersion() {
        return latestSupported();
    }


}
